//
//  Room.m
//  Wi-Fi Party remade-1
//
//  Created by Vieta Ken on 2021/2/26.
//

#import <Foundation/Foundation.h>
#import "Room.h"
OSStatus playCallback(void *inRefCon,AudioUnitRenderActionFlags *ioActionFlags,const AudioTimeStamp *inTimeStamp,UInt32 inBusNumber,UInt32 inNumberFrames,AudioBufferList *ioData);
OSStatus recordCallback(void *inRefCon,AudioUnitRenderActionFlags *ioActionFlags,const AudioTimeStamp *inTimeStamp,UInt32 inBusNumber,UInt32 inNumberFrames,AudioBufferList *ioData);
NSTimeInterval realIOBufferDuration;

@interface Room()
@end

@implementation Room

-(id)init{
    self=[super init];
    {   //Initialize some flags
        self->speaking=false;
        self->alive=true;
    }
    {   //Stream Format
        memset(&self->stream_format, 0, sizeof(self->stream_format));
        self->stream_format.mBitsPerChannel=BYTES_PER_FRAME*8;
        self->stream_format.mChannelsPerFrame=1;
        self->stream_format.mFramesPerPacket=1;
        self->stream_format.mBytesPerFrame=BYTES_PER_FRAME;
        self->stream_format.mBytesPerPacket=self->stream_format.mBytesPerFrame*self->stream_format.mFramesPerPacket;
        self->stream_format.mSampleRate=SAMPLE_RATE;
        self->stream_format.mFormatID=kAudioFormatLinearPCM;
        self->stream_format.mFormatFlags=kLinearPCMFormatFlagIsPacked|kLinearPCMFormatFlagIsSignedInteger;
    }
    {   //AVAudioSession
        NSError *err;
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&err];
        check_nil(err, "Failed to set AVAudioSession category");
        [[AVAudioSession sharedInstance] setPreferredIOBufferDuration:BUFFER_DELAY error:&err];
        realIOBufferDuration=[AVAudioSession sharedInstance].IOBufferDuration;
        check_nil(err, "Failed to set preferred IOBufferDuration");
        [[AVAudioSession sharedInstance] setActive:true error:&err];
        check_nil(err, "Failed to activate AVAudioSession");
    }
    {   //AudioUnit
        //Get an AudioUnit object
        AudioComponentDescription desc;
        desc.componentType=kAudioUnitType_Output;
        desc.componentSubType=kAudioUnitSubType_RemoteIO;
        desc.componentManufacturer=kAudioUnitManufacturer_Apple;
        desc.componentFlags=0;
        desc.componentFlagsMask=0;
        
        AudioComponent component;
        component=AudioComponentFindNext(nil, &desc);
        
        check_int(AudioComponentInstanceNew(component, &self->audio_unit),
                  0, "Failed to get AudioUnit");
        
        //Set stream format
        check_int(AudioUnitSetProperty(self->audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &self->stream_format, sizeof(self->stream_format)),
                  0, "Failed to set input format");
        check_int(AudioUnitSetProperty(self->audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &self->stream_format, sizeof(self->stream_format)),
                  0, "Failed to set output format");
        
        //Set callbacks
        AURenderCallbackStruct callback;
        callback.inputProcRefCon=(__bridge  void *)self;
        callback.inputProc=recordCallback;
        check_int(AudioUnitSetProperty(self->audio_unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Output, 1, &callback, sizeof(callback)),
                  0, "Error setting record callback");
        callback.inputProc=playCallback;
        check_int(AudioUnitSetProperty(self->audio_unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callback, sizeof(callback)),
                  0, "Error setting play callback");
        
        //Final steps
        AudioUnitInitialize(self->audio_unit);
        //Start the AudioUnit at last (just before this init funciton ends)
    }
    {   //Buffer & Recv_dict
        self->play_buffer=[[Queue alloc] init];
        self->recv_dict=[[NSMutableDictionary alloc] init];
    }
    {   //inputMixer
        self->mixer=[[inputMixer alloc] init];
    }
    {   //dispatch_queue
        self->dispatch_queue=dispatch_queue_create("room dispatch queue", DISPATCH_QUEUE_CONCURRENT);
    }
    {   //receive_socket
        self->receive_socket=socket(AF_INET, SOCK_DGRAM, 0);
        struct sockaddr_in listen_addr={0};
        listen_addr.sin_family=AF_INET;
        listen_addr.sin_port=htons(PORT);
        listen_addr.sin_addr.s_addr=htonl(INADDR_ANY);
        check_bool(bind(self->receive_socket, (struct sockaddr*)&listen_addr, sizeof(listen_addr)) >= 0
                   , "Failed to set up UDP listener");
        dispatch_async(self->dispatch_queue, ^{
            while(self->alive) receive_from_udp(self->receive_socket, self->recv_dict, self->dispatch_queue);
        });
    }
    
    AudioOutputUnitStart(self->audio_unit);
    return self;
}

-(void)speak{
    NSError *err;
    self->speaking=true;
    {
        AudioOutputUnitStop(self->audio_unit);
        AudioUnitUninitialize(self->audio_unit);
    }
    {
        [[AVAudioSession sharedInstance] setActive:false error:&err];
        check_nil(err, "Error deactivating AVAudioSession");
    }
    {
        UInt32 flag=1;
        check_int(AudioUnitSetProperty(self->audio_unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flag, sizeof(flag)),
                  0,"Error enabling recording for AudioUnit");
    }
    {
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:&err];
        check_nil(err, "Error setting AVAudioSessionCategory to PlayAndRecord");
        [[AVAudioSession sharedInstance] setActive:true error:&err];
        check_nil(err, "Error re-activating AVAudioSession");
    }
    {
        AudioUnitInitialize(self->audio_unit);
        AudioOutputUnitStart(self->audio_unit);
    }
}

-(void)unspeak{
    NSError *err;
    self->speaking=false;
    {
        AudioOutputUnitStop(self->audio_unit);
        AudioUnitUninitialize(self->audio_unit);
    }
    {
        [[AVAudioSession sharedInstance] setActive:false error:&err];
        check_nil(err, "Error deactivating AVAudioSession");
    }
    {
        UInt32 flag=0;
        check_int(AudioUnitSetProperty(self->audio_unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flag, sizeof(flag)),
                  0,"Error disabling recording for AudioUnit");
    }
    {
        [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&err];
        check_nil(err, "Error setting AVAudioSessionCategory to Playback");
        [[AVAudioSession sharedInstance] setActive:true error:&err];
        check_nil(err, "Error re-activating AVAudioSession");
    }
    {
        AudioUnitInitialize(self->audio_unit);
        AudioOutputUnitStart(self->audio_unit);
    }
}

-(void)leave{
    
    AudioOutputUnitStop(self->audio_unit);
    {
        NSError *err;
        [[AVAudioSession sharedInstance] setActive:false error:&err];
        check_nil(err, "Error deactivating AVAudioSession when leaving");
    }
    close(self->receive_socket);
}
-(void)dealloc{
    [self leave];
}
@end



OSStatus playCallback(void *inRefCon,
                      AudioUnitRenderActionFlags *ioActionFlags,
                      const AudioTimeStamp *inTimeStamp,
                      UInt32 inBusNumber,
                      UInt32 inNumberFrames,
                      AudioBufferList *ioData){
    Room *room=(__bridge Room *)(inRefCon);
    AudioBuffer *outputBuffer=&ioData->mBuffers[0];
    cleanup(room->recv_dict);
    mix_audio_streams([room->recv_dict allValues], outputBuffer->mDataByteSize, room->play_buffer);
    [room->play_buffer readAndWriteTo:outputBuffer->mData size:outputBuffer->mDataByteSize];
    room->mixer->length=outputBuffer->mDataByteSize;
    [room->mixer flush];
    
    return noErr;
}

OSStatus recordCallback(void *inRefCon,
                        AudioUnitRenderActionFlags *ioActionFlags,
                        const AudioTimeStamp *inTimeStamp,
                        UInt32 inBusNumber,
                        UInt32 inNumberFrames,
                        AudioBufferList *ioData){
    Room *room=(__bridge Room *)(inRefCon);
    AudioBufferList* bufferlist=malloc(sizeof(AudioBufferList));
    bufferlist->mNumberBuffers=1;
    AudioBuffer* render_buffer=&bufferlist->mBuffers[0];
    render_buffer->mNumberChannels=room->stream_format.mChannelsPerFrame;
    render_buffer->mDataByteSize=inNumberFrames*room->stream_format.mBytesPerFrame;
    render_buffer->mData=malloc(render_buffer->mDataByteSize);
    check_int(AudioUnitRender(room->audio_unit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, bufferlist),
              noErr,"Failed to render recorded audio");
    dispatch_async(room->dispatch_queue, ^{
        [room->mixer addAudioSegment:render_buffer->mData ofSize:render_buffer->mDataByteSize from:@"microphone"];
        
        free(render_buffer->mData);
        free(bufferlist);
    });
    
    return noErr;
}
